﻿// --------------------------------------------------------------------------------------------------------------------
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
// <credits>
// David Anson
// http://blogs.msdn.com/b/delay/archive/2010/08/18/fade-into-you-code-to-easily-animate-orientation-changes-for-windows-phone-applications-now-supports-fade-too.aspx
// </credits>
// --------------------------------------------------------------------------------------------------------------------


namespace WP7Contrib.View.Controls.Orientation
{
    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Controls.Primitives;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Media.Imaging;
    using System.Windows.Shapes;

    using Microsoft.Phone.Controls;

    /// <summary>
    /// PhoneApplicationFrame subclass that fades between device orientation changes.
    /// </summary>
    public class FadeOrientationChangesFrame : PhoneApplicationFrame
    {
        /// <summary>
        /// Stores the Popup for displaying the "before" content.
        /// </summary>
        private readonly Popup popup = new Popup();

        /// <summary>
        /// Stores the Storyboard used to animate the transition.
        /// </summary>
        private readonly Storyboard storyboard = new Storyboard();

        /// <summary>
        /// Stores the Timeline used to perform the transition.
        /// </summary>
        private readonly DoubleAnimation animation = new DoubleAnimation { From = 1, To = 0 };

        /// <summary>
        /// Stores the previous orientation.
        /// </summary>
        private PageOrientation previousOrientation = PageOrientation.PortraitUp;

        /// <summary>
        /// Initializes a new instance of the FadeOrientationChangesFrame class.
        /// </summary>
        public FadeOrientationChangesFrame()
        {
            // Set up animation
            Storyboard.SetTargetProperty(this.animation, new PropertyPath(UIElement.OpacityProperty));
            this.storyboard.Children.Add(this.animation);
            this.storyboard.Completed += this.HandleStoryboardCompleted;

            // Initialize variables
            EasingFunction = new QuadraticEase(); // Initialized here to avoid a single shared instance

            // Hook events
            OrientationChanged += this.HandleOrientationChanged;
        }

        /// <summary>
        /// Gets or sets a value indicating whether animation is enabled.
        /// </summary>
        public bool IsAnimationEnabled
        {
            get { return (bool)GetValue(IsAnimationEnabledProperty); }
            set { SetValue(IsAnimationEnabledProperty, value); }
        }
        
        /// <summary>
        /// Identifies the IsAnimationEnabled DependencyProperty.
        /// </summary>
        public static readonly DependencyProperty IsAnimationEnabledProperty =
            DependencyProperty.Register("IsAnimationEnabled", typeof(bool), typeof(FadeOrientationChangesFrame), new PropertyMetadata(true));

        /// <summary>
        /// Gets or sets a value indicating the duration of the orientation change animation.
        /// </summary>
        public TimeSpan Duration
        {
            get { return (TimeSpan)GetValue(DurationProperty); }
            set { SetValue(DurationProperty, value); }
        }
        
        /// <summary>
        /// Identifies the Duration DependencyProperty.
        /// </summary>
        public static readonly DependencyProperty DurationProperty =
            DependencyProperty.Register("Duration", typeof(TimeSpan), typeof(FadeOrientationChangesFrame), new PropertyMetadata(TimeSpan.FromSeconds(0.4)));

        /// <summary>
        /// Gets or sets a value indicating the IEasingFunction to use for the orientation change animation.
        /// </summary>
        public IEasingFunction EasingFunction
        {
            get { return (IEasingFunction)GetValue(EasingFunctionProperty); }
            set { SetValue(EasingFunctionProperty, value); }
        }
        
        /// <summary>
        /// Identifies the EasingFunction DependencyProperty.
        /// </summary>
        public static readonly DependencyProperty EasingFunctionProperty =
            DependencyProperty.Register("EasingFunction", typeof(IEasingFunction), typeof(FadeOrientationChangesFrame), new PropertyMetadata(null));

        /// <summary>
        /// Handles the OrientationChanged event.
        /// </summary>
        /// <param name="sender">Event source.</param>
        /// <param name="e">Event arguments.</param>
        private void HandleOrientationChanged(object sender, OrientationChangedEventArgs e)
        {
            // Stop/complete Storyboard in case it's active
            this.storyboard.Stop();
            this.HandleStoryboardCompleted(null, null);

            if (this.IsAnimationEnabled)
            {
                // Get "before" width/height
                bool normal = PageOrientation.Portrait == (PageOrientation.Portrait & this.previousOrientation);
                var width = normal ? ActualWidth : ActualHeight;
                var height = normal ? ActualHeight : ActualWidth;

                // Capture "before" visuals in a WriteableBitmap
                var writeableBitmap = new WriteableBitmap((int)width, (int)height);
                writeableBitmap.Render(this, null);
                writeableBitmap.Invalidate();

                // Create transforms for "before" content
                var transforms = new TransformGroup();
                switch (this.previousOrientation)
                {
                    case PageOrientation.LandscapeLeft:
                        transforms.Children.Add(new RotateTransform { Angle = 90 });
                        transforms.Children.Add(new TranslateTransform { X = this.ActualWidth });
                        break;
                    case PageOrientation.LandscapeRight:
                        transforms.Children.Add(new RotateTransform { Angle = -90 });
                        transforms.Children.Add(new TranslateTransform { Y = this.ActualHeight });
                        break;
                }

                // Create content with default background and WriteableBitmap overlay for "before"
                var container = new Grid
                {
                    Width = width,
                    Height = height,
                    Background = (Brush)Application.Current.Resources["PhoneBackgroundBrush"],
                    RenderTransform = transforms,
                    IsHitTestVisible = false,
                };
                
                var content = new Rectangle
                {
                    Fill = new ImageBrush
                    {
                        ImageSource = writeableBitmap,
                        Stretch = Stretch.None,
                    }
                };
                container.Children.Add(content);

                // Configure Popup for displaying "before" content
                this.popup.Child = container;
                this.popup.IsOpen = true;

                // Update and play the animation to fade from "before" to "after"
                Storyboard.SetTarget(this.animation, container);
                this.animation.Duration = this.Duration;
                this.animation.EasingFunction = this.EasingFunction;
                this.storyboard.Begin();
            }

            // Save current orientation for next time
            this.previousOrientation = e.Orientation;
        }

        /// <summary>
        /// Handles the completion of the Storyboard.
        /// </summary>
        /// <param name="sender">Event source.</param>
        /// <param name="e">Event arguments.</param>
        private void HandleStoryboardCompleted(object sender, EventArgs e)
        {
            // Remove and clear Popup
            this.popup.IsOpen = false;
            this.popup.Child = null;
        }
    }
}
